package tr.edu.ege.kipler.satisfiability.container;

import java.util.HashSet;
import java.util.Set;

import tr.edu.ege.kipler.common.Copyable;
import tr.edu.ege.kipler.common.RelationalModel;
import tr.edu.ege.kipler.common.RelationalNode;
import tr.edu.ege.kipler.satisfiability.Reasoner;
import tr.edu.ege.kipler.satisfiability.container.creational.WorldFactory;

/**
 * A world constraint system can be seen as a graph where each node is a world
 * and each edge is directed having a label that specifies the modal operator
 * (relation) and the agent.
 * 
 * @author İnanç Seylan
 * 
 */
public class WorldConstraintSystem implements Copyable<WorldConstraintSystem> {

	/**
	 * Each world in a world constraint system is named as "w" + n where n is
	 * the number of the last named world plus one. This field stores the last
	 * named world's number.
	 */
	private int lastWorldNo;

	private int id;

	private RelationalModel<World, String, String> worldG = new RelationalModel<World, String, String>();

	private Reasoner reasoner;

	private WorldFactory worldCreator;

	private WcsData data;

	/**
	 * Default constructor
	 */
	public WorldConstraintSystem(WorldFactory worldCreator) {
		this.worldCreator = worldCreator;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	/**
	 * This method is used for testing if a world with the given name exists in
	 * this constraint system.
	 * 
	 * @param worldName
	 *            The world name to be tested for existence in the constraint
	 *            system
	 * @return true if the world with the given name exists in this constraint
	 *         system.
	 */
	public boolean contains(String worldName) {
		return worldG.contains(worldName);
	}

	public Set<World> getAllWorlds() {
		Set<World> result = new HashSet<World>();
		for (RelationalNode<World, String, String> node : worldG.getNodes()) {
			result.add(node.getValue());
		}
		return result;
	}

	/**
	 * @param worldName
	 *            The name of the world to be returned
	 * @return the world with the given name.
	 */
	public World getWorld(String worldName) {
		return worldG.getNode(worldName).getValue();
	}

	/**
	 * Adds a world access information to this constraint system.
	 * 
	 * @param wa
	 *            The world access information
	 */
	public void addConstraint(WorldAccess wa) {
		worldG.getNode(wa.getFrom().getKey()).addOutgoing(wa.getRelation(),
				worldG.getNode(wa.getTo().getKey()));
	}

	public World createWorld() {
		World world = worldCreator.createWorld();
		world.setName(generateWorldName());
		world.wcs = this;
		worldG.add(new RelationalNode<World, String, String>(world));
		return world;
	}

	public Set<World> getAccWorldsByFrom(String relation, World from) {
		Set<World> result = new HashSet<World>();
		for (RelationalNode<World, String, String> node : worldG.getNode(
				from.getKey()).getOutgoing(relation)) {
			result.add(node.getValue());
		}
		return result;
	}

	/**
	 * Generates the next world name, incrementing {@link #lastWorldNo}. The
	 * naming rule is explained in {@link #lastWorldNo}.
	 * 
	 * @return the generated world name.
	 */
	private String generateWorldName() {
		return "w" + (lastWorldNo++);
	}

	public Reasoner getReasoner() {
		return reasoner;
	}

	public void setReasoner(Reasoner reasoner) {
		this.reasoner = reasoner;
	}

	/**
	 * @return the data
	 */
	public WcsData getData() {
		return data;
	}

	/**
	 * @param data
	 *            the data to set
	 */
	public void setData(WcsData data) {
		this.data = data;
	}

	public WorldConstraintSystem copy() {
		WorldConstraintSystem result = new WorldConstraintSystem(worldCreator);
		result.id = id;
		result.lastWorldNo = lastWorldNo;
		result.worldG = worldG.copy();
		for (RelationalNode<World, String, String> node : result.worldG
				.getNodes()) {
			node.getValue().wcs = result;
		}
		result.setData(getData().copy());
		return result;
	}

	/**
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append("WCS " + getId() + " { ");
		for (World world : getAllWorlds()) {
			buffer.append(world);
			buffer.append("\n");
		}
		// TODO print accessibility relations too
		buffer.append(" }");
		return buffer.toString();
	}

	@Override
	public boolean equals(Object obj) {
		if (obj != null && (obj.getClass().equals(this.getClass()))) {
			WorldConstraintSystem other = (WorldConstraintSystem) obj;
			return id == other.id && lastWorldNo == other.lastWorldNo
					&& worldG.equals(other.worldG);
		}
		return false;
	}

	@Override
	public int hashCode() {
		return 17 * 37 + getId();
	}

}
